package com.jingdianjichi.auth.domain.service.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Preconditions;
import com.jingdianjichi.auth.common.enums.AuthUserStatusEnum;
import com.jingdianjichi.auth.common.enums.IsDeletedFlagEnum;
import com.jingdianjichi.auth.common.util.RedisUtil;
import com.jingdianjichi.auth.domain.constants.AuthConstant;
import com.jingdianjichi.auth.domain.convert.AuthUserBOConverter;
import com.jingdianjichi.auth.domain.entity.AuthUserBO;
import com.jingdianjichi.auth.domain.service.AuthUserDomainService;
import com.jingdianjichi.auth.infra.basic.entity.*;
import com.jingdianjichi.auth.infra.basic.service.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

/**
 * 用户领域服务实现类
 * 负责处理用户相关的核心业务逻辑，包括用户注册、更新、删除等操作
 * 实现了领域驱动设计(DDD)中的领域服务层，封装了复杂的业务规则和逻辑
 * 
 * @author ChickenWing
 * @date 2023/10/28
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class AuthUserDomainServiceImpl implements AuthUserDomainService {

    /**
     * 用户基础服务
     * 注入基础设施层的用户服务，用于执行数据库操作
     */

    private final AuthUserRoleService authUserRoleService;

    private final AuthUserService authUserService;

    private final AuthRoleService authRoleService;

    private final AuthRolePermissionService authRolePermissionService;

    private final AuthPermissionService authPermissionService;

    private final RedisUtil redisUtil;



    private String authPermissionPrefix = "auth.permission";

    private String authRolePrefix = "auth.role";

    private static final String LOGIN_PREFIX = "loginCode";

    /**
     * 密码加密盐值
     * 用于增强密码安全性的固定盐值，实际项目中建议使用配置文件或随机盐值
     */
    private String salt = "chicken";

    /**
     * 用户注册业务逻辑
     * 处理用户注册的核心业务流程，包括密码加密、状态设置、数据持久化等
     * 
     * @param authUserBO 用户业务对象，包含注册所需的用户信息
     * @return Boolean 注册结果，true表示注册成功，false表示注册失败
     */
    @Override
    @SneakyThrows
    @Transactional
    public Boolean register(AuthUserBO authUserBO) {

        //校验用户是否存在
        AuthUser existAuthUser = new AuthUser();
        existAuthUser.setUserName(authUserBO.getUserName());
        List<AuthUser> existUser = authUserService.queryByCondition(existAuthUser);
        if (existUser.size() > 0) {
            return true;
        }

        // 1. 创建并保存用户基础信息
        AuthUser authUser = createAndSaveUser(authUserBO);

        // 2. 获取默认角色信息
        AuthRole roleResult = getDefaultRole();

        // 3. 建立用户与角色的关联关系
        createUserRoleAssociation(authUser.getId(), roleResult.getId());
        
        // 4. 查询并获取角色对应的权限信息
        List<AuthPermission> authPermissions = getRolePermissions(roleResult.getId());
        
        // 5. 同步用户角色和权限信息到Redis缓存
        syncUserCacheData(authUser.getUserName(), roleResult, authPermissions);
        
        return true;
    }

    /**
     * 创建并保存用户基础信息
     * 
     * @param authUserBO 用户业务对象
     * @return AuthUser 保存后的用户实体
     */
    private AuthUser createAndSaveUser(AuthUserBO authUserBO) {
        // 将业务对象BO转换为数据实体Entity，用于数据库操作
        AuthUser authUser = new AuthUser();
        BeanUtils.copyProperties(authUserBO, authUser);
        
        // 使用Sa-Token的安全工具对密码进行MD5加盐加密，提高密码安全性（这里运用验证码登录，不需要密码）
        // authUser.setPassword(SaSecureUtil.md5BySalt(authUser.getPassword(), salt));
        
        // 设置用户状态为启用状态
        authUser.setStatus(AuthUserStatusEnum.OPEN.getCode());
        // 设置删除标志为未删除状态（软删除机制）
        authUser.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        
        // 调用基础设施层服务执行数据库插入操作
        Integer count = authUserService.insert(authUser);
        // 检查注册结果是否成功
        Preconditions.checkState(count > 0, "注册失败");
        
        return authUser;
    }

    /**
     * 获取默认角色信息
     * 
     * @return AuthRole 默认角色实体
     */
    private AuthRole getDefaultRole() {
        // 建立用户与角色的初步关联关系
        AuthRole authRole = new AuthRole();
        // 默认设置为普通用户
        authRole.setRoleKey(AuthConstant.NORMAL_USER);
        
        // 从角色表中查出关于普通用户的数据，用于后续关联
        AuthRole roleResult = authRoleService.queryByCondition(authRole);
        // 检查查询结果是否为空
        Preconditions.checkNotNull(roleResult, "普通用户角色不存在");
        
        return roleResult;
    }

    /**
     * 建立用户与角色的关联关系
     * 
     * @param userId 用户ID
     * @param roleId 角色ID
     */
    private void createUserRoleAssociation(Long userId, Long roleId) {
        // 建立用户与角色的关联关系
        AuthUserRole authUserRole = new AuthUserRole();
        // 关联用户id
        authUserRole.setUserId(userId);
        // 关联角色id
        authUserRole.setRoleId(roleId);
        // 设置删除标志为未删除状态
        authUserRole.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        // 调用基础设施层服务执行数据库插入操作
        Integer count = authUserRoleService.insert(authUserRole);
        // 检查注册结果是否成功
        Preconditions.checkState(count > 0, "注册失败");
    }

    /**
     * 查询角色对应的权限信息
     * 
     * @param roleId 角色ID
     * @return List<AuthPermission> 权限列表
     */
    private List<AuthPermission> getRolePermissions(Long roleId) {
        // 查询用户角色对应的权限
        List<AuthRolePermission> authRolePermissions = authRolePermissionService.queryByRoleId(roleId);
        // 检查查询结果是否为空
        Preconditions.checkNotNull(authRolePermissions, "普通用户角色权限不存在");
        
        // 根据查询出来的权限id，查询出权限的信息
        List<AuthPermission> authPermissions = authPermissionService.queryByIds(authRolePermissions);
        
        return authPermissions;
    }

    /**
     * 同步用户角色和权限信息到Redis缓存
     * 
     * @param userName 用户名
     * @param roleResult 角色信息
     * @param authPermissions 权限列表
     */
    private void syncUserCacheData(String userName, AuthRole roleResult, List<AuthPermission> authPermissions) {
        // 将当前用户的角色和权限信息同步到Redis缓存中
        // 构建角色缓存键，格式为：auth.role:用户名
        String roleKey = redisUtil.buildKey(authRolePrefix, userName);
        // 构建权限缓存键，格式为：auth.permission:角色名
        String permissionKey = redisUtil.buildKey(authPermissionPrefix, roleResult.getRoleKey());
        
        try {
            // 同步用户角色到Redis缓存，使用JSON格式存储
            List<String> roleList = new LinkedList<>();
            roleList.add(roleResult.getRoleKey());
            redisUtil.set(roleKey, JSON.toJSONString(roleList));

            // 同步用户权限到Redis缓存，使用JSON格式存储
            LinkedList<String> permissionslist = new LinkedList<>();
            authPermissions.forEach(authPermission -> {
                permissionslist.add(authPermission.getPermissionKey());
            });
            redisUtil.set(permissionKey, JSON.toJSONString(permissionslist));
        } catch (Exception e) {
            // 如果缓存操作失败，清理可能已经写入的缓存数据
            log.error("缓存同步失败", e);
            redisUtil.del(roleKey);
            redisUtil.del(permissionKey);
            throw new RuntimeException("缓存同步失败", e);
        }
    }


    /**
     * 用户登录业务逻辑
     * 处理用户登录的业务流程，包括验证码验证、用户注册（如果不存在）、登录认证等
     *
     * @param captcha 用户输入的验证码
     * @return SaTokenInfo 登录成功后的令牌信息，包含用户会话标识和权限信息
     */
    @Override
    public SaTokenInfo login(String captcha) {
        //校验验证码是否存在对应用户
        String key = redisUtil.buildKey(LOGIN_PREFIX, captcha);
        //若不存在则抛异常
        Preconditions.checkArgument(redisUtil.get(key) != null,"验证码验证错误");
        //若存在，则取出对应openid
        String openid = redisUtil.get(key);
        //用openid创建bo对象调用注册接口
        AuthUserBO authUserBO = new AuthUserBO();
        authUserBO.setUserName(openid);
        this.register(authUserBO);
        //注册成功后调用sa-token的登录接口
        StpUtil.login(openid);
        //登录成功后返回token
        SaTokenInfo saTokenInfo = StpUtil.getTokenInfo();
        return saTokenInfo;
    }

    @Override
    public AuthUserBO getUserInfo(AuthUserBO authUserBO) {
        AuthUser authUser = new AuthUser();
        authUser.setUserName(authUserBO.getUserName());
        List<AuthUser> userList = authUserService.queryByCondition(authUser);
        if(CollectionUtils.isEmpty(userList)){
            return new AuthUserBO();
        }
        AuthUser user = userList.get(0);
        BeanUtils.copyProperties(user,authUserBO);
        return authUserBO;
    }

    @Override
    public Boolean update(AuthUserBO authUserBO) {
        AuthUser authUser = AuthUserBOConverter.INSTANCE.convertBOToEntity(authUserBO);
        Integer count = authUserService.updateByUserName(authUser);
        return count > 0;
    }
}
